﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.TraceData;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Components;

namespace Noris.Tools.TraceVisualiser.Green
{
    /// <summary>
    /// Funkce, která zobrazí uživatele, kteří se účastní blokování
    /// </summary>
    public class Func_ShowBlockChain : Func_Base, IFunctionContextMenuItem
    {
        #region IPlugin Members
        /// <summary>
        /// Příznak, že Plugin je aktivní ve standardním běhu (tj. při běžném užiavtelském spuštění).
        /// Plně funkční pluginy tedy vrací true.
        /// Pluginy, které jsou ve vývoji, vrací false, takové jsou zobrazeny / spouštěny pouze v aplikaci, která běží v režimu Debug.
        /// </summary>
        bool IPlugin.Active { get { return false; } }
        #endregion
        #region IFunctionContextMenuItem Members
        bool IFunctionContextMenuItem.IsFunctionSuitableFor(FunctionContextMenuItemSuitableArgs args)
        {
            /*
            args.MenuItems.Add(new FunctionMenuItem(70, "Kdo to blokuje?",
                "Najde uživatele, kteří se účastní blokování.",
                true, Noris.Tools.TraceVisualiser.Pics.PicLibrary16.ChartOrganisationDelete_FromFile, null));
            */
            args.MenuItems.Add(new FunctionMenuItem(70, "Zobrazit účastníky blokování",
                "Pokusí se najít SQL příkazy delší než 2 sekundy, které se vzájemně překrývají,\r\na zafiltrovat uživatele, kteří jsou jejich autory.",
                true, Noris.Tools.TraceVisualiser.Pics.PicLibrary16.GnomePlanner_FromFile, null));

            return true;
        }
        bool IFunctionContextMenuItem.IsMenuItemEnabledFor(FunctionContextMenuItemRunArgs args)
        {
            // Pokud projekt neobsahuje data o blokování, nerozsvítím funkci v menu (detekční metoda nenačítá data o blokování):
            if (!this.DetectDbBlockingTraceExists(this.GetDataSource(args).Main))
                return false;

            // Pokud uživatel nevybral nic, z čeho by šlo začít, taky nerozsvítím funkci v menu:
            if (!this.DetectDbBlockingEventSelected(args))
                return false;

            // OK, funkci lze spustit:
            return true;
        }
        void IFunctionContextMenuItem.Run(FunctionContextMenuItemRunArgs args)
        {
            try
            {
                this.TraceDataMain = this.GetDataSource(args).Main;

                List<EventInfo> blockList = this.CollectBlock(args);
                List<NodeInfo> nodes = this.TraceDataMain.NodeRoot.GetAllNodes();
                args.Result.DocPointer = this.CreateResultPointer(blockList, nodes);

            }
            finally
            {
                this.TraceDataMain = null;
            }
        }
        /// <summary>
        /// Vytvoří a vrátí Pointer, který obsahuje popis toho, jak má vypadat graf po skončení funkce.
        /// Pointer deklaruje: časový úsek, explicitní filtr, a seznam otevřených nodů.
        /// </summary>
        /// <param name="blockList"></param>
        /// <param name="nodes"></param>
        /// <returns></returns>
        private TraceDocPointer CreateResultPointer(List<EventInfo> blockList, List<NodeInfo> nodes)
        {
            TraceDocPointer pointer = new TraceDocPointer();

            // TimeRange má pokrývat souhrn času jednotlivých bloků:
            TimeRange time = TimeRange.Empty;
            foreach (EventInfo evnt in blockList)
                time = time + evnt.TimeWithOffset;
            pointer.Time = time;

            // Explicitní filtr vybírá řádky odpovídající blokování (podřízené řádky se vyberou automaticky):
            // Současně generujeme seznam otevřených nodů:
            string nodeUser = AplServer.NorisAplConstants.NodeUser.ToLower();
            string nodeSpid = AplServer.NorisAplConstants.SubNodeAplSql.ToLower();
            string nodeBlock = DbBlocking.NorisBlockingConstants.NodeBlocking.ToLower();
            pointer.ExpandedNodes.Add(nodeUser);
            pointer.ExpandedNodes.Add(nodeBlock);
            pointer.RowFilter = new RowFilter();
            pointer.RowFilter.ExplicitRowList = new List<GID>();
            foreach (NodeInfo nodeInfo in nodes)
            {
                if (nodeInfo.IsRoot) continue;            // Root vynechávám, vyřeší se sám. V dalších příkazech by se musel testovat, protože občas zjišťuji "nodeInfo.Parent.Key", a Root nemá objekt Parent.

                bool addGId = false;
                bool expNod = false;

                // Pokud je to Node, jehož přímým parentem je NodeUser, a jeho Key je některý z Users uvedených v seznamu blokování, pak jej povolíme:
                if (nodeInfo.IsChildOfParent(nodeUser, false, true))
                {   // Node je řádkem konkrétního uživatele:
                    bool addUser = nodeInfo.Key.ToLower().StartsWith("k");
                    addGId |= addUser;
                    expNod |= addUser;
                }
                else if (nodeInfo.IsChildOfParent(nodeSpid, true, true))
                {   // Node je řádkem (kdekoliv) pod řádkem "SQL SEREVR" = jde o řádek se SPID:
                    int spid;
                    if (Int32.TryParse(nodeInfo.Key, out spid))
                    {
                        bool addSpid = (spid <= 85);
                        addGId |= addSpid;
                    }
                }
                else if (nodeInfo.IsChildOfParent(nodeBlock, true, true))
                {   // Node je řádkem (kdekoliv) pod řádkem "Blokování" = jde o řádek s názvem databáze nebo o řádek s názvem objektu:
                    // Povolím jej, pokud jde o objekt uvedený v logu blokování:
                    bool addBlok = nodeInfo.Key.ToLower().StartsWith("subj");
                    addGId |= addBlok;
                }

                if (addGId)
                    pointer.RowFilter.ExplicitRowList.Add(nodeInfo.GId);
                if (expNod)
                    pointer.ExpandedNodes.Add(nodeInfo.FullKey.ToLower());
            }

            // Nastavení pozice řádku:
            pointer.Name = "Výsledek funkce";
            pointer.FirstVisibleNode = nodeUser;
            pointer.FirstVisiblePixel = 0;

            return pointer;
        }
        #endregion
        #region Detekce, zda existují podklady pro hledání blokování
        /// <summary>
        /// Zjistí, zda v trace souborech je nějaký soubor popisující blokování
        /// </summary>
        /// <param name="traceDataMain"></param>
        /// <returns></returns>
        protected bool DetectDbBlockingTraceExists(MainInfo traceDataMain)
        {
            return traceDataMain.Servers.Any(server => String.Equals(server.ServerName, Green.DbBlocking.NorisBlockingConstants.ServerName, StringComparison.InvariantCultureIgnoreCase));
        }
        /// <summary>
        /// Zjistí, zda mezi vybranými elementy je nějaký, na jehož základě mohu hledat blokování (obsahuje odkaz na SQL PID)
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        protected bool DetectDbBlockingEventSelected(FunctionContextMenuItemRunArgs args)
        {
            // Vyhledám první event z logu blokování:
            List<DbBlocking.EventItem> dbBlockItems = this.GetInputEventsOfType<DbBlocking.EventItem>(args, 1);
            if (dbBlockItems != null && dbBlockItems.Count > 0) return true;

            // Vyhledám první SQL event:
            List<ISqlEventData> iSqlItems = this.GetInputEventsOfType<ISqlEventData>(args, 1);
            if (iSqlItems != null && iSqlItems.Count > 0) return true;

            // Vyhledám první událost AplServer.EventTransItem:
            List<AplServer.EventTransItem> aplTransItems = this.GetInputEventsOfType<AplServer.EventTransItem>(args, 1);
            if (aplTransItems != null && aplTransItems.Count > 0) return true;

            return false;
        }
        #endregion
        #region Vyhledání bloků, verze 3
        /// <summary>
        /// Najde a vrátí seznam položek blokování pro vstupující záznamy.
        /// Vstupující záznamy mohou být buď DbBlocking.EventItem, anebo ISql
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        private List<EventInfo> CollectBlock(FunctionContextMenuItemRunArgs args)
        {
            Dictionary<int, EventInfo> scannedEventsDict = new Dictionary<int, EventInfo>();
            List<EventInfo> eventInfoList;

            // Načtu si položky, které uživatel vybral, a které jsou druhu DbBlocking.EventItem:
            eventInfoList = this.GetInputEventInfos(args, ei => (ei is DbBlocking.EventItem));
            foreach (EventInfo eventInfo in eventInfoList)
                this.CollectBlockForEventDbBlock(eventInfo, scannedEventsDict);

            // Načtu si položky, které uživatel vybral, a které jsou druhu DbBlocking.EventItem:
            eventInfoList = this.GetInputEventInfos(args, ei => (ei is ISqlEventData));
            foreach (EventInfo eventInfo in eventInfoList)
                this.CollectBlockForEventISqlEvent(eventInfo, scannedEventsDict);

            // Načtu si položky, které uživatel vybral, a které jsou druhu AplServer.EventTransItem:
            eventInfoList = this.GetInputEventInfos(args, ei => (ei is AplServer.EventTransItem));
            foreach (EventInfo eventInfo in eventInfoList)
                this.CollectBlockForEventAplTrans(eventInfo, scannedEventsDict);

            return new List<EventInfo>(scannedEventsDict.Values);
        }
        /// <summary>
        /// Zpracuje (najde položky blokování) pro danou událost typu DbBlocking.EventItem
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <param name="scannedEventsDict"></param>
        private void CollectBlockForEventDbBlock(EventInfo eventInfo, Dictionary<int, EventInfo> scannedEventsDict)
        {
            DbBlocking.EventItem block = eventInfo.EventItem as DbBlocking.EventItem;
            if (block != null)
            {
                // Zvětší interval (čas s korekcí) o 1 sec na každou stranu:
                TimeRange timeRange = eventInfo.TimeWithOffset;
                timeRange.Change(-1d, +1d);

                this.CollectBlockForSpid(block.Process2Active.SpId, timeRange, scannedEventsDict);
                this.CollectBlockForSpid(block.Process1Blocked.SpId, timeRange, scannedEventsDict);
            }
        }
        /// <summary>
        /// Zpracuje (najde položky blokování) pro danou událost typu ISqlEventData
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <param name="scannedEventsDict"></param>
        private void CollectBlockForEventISqlEvent(EventInfo eventInfo, Dictionary<int, EventInfo> scannedEventsDict)
        {
            ISqlEventData sqlEvent = eventInfo.EventItem as ISqlEventData;
            if (sqlEvent != null)
            {
                // Zvětší interval (čas s korekcí) o 1 sec na každou stranu:
                TimeRange timeRange = eventInfo.TimeWithOffset;
                timeRange.Change(-1d, +1d);

                this.CollectBlockForSpid(sqlEvent.SqlPId, timeRange, scannedEventsDict);
            }
        }
        /// <summary>
        /// Zpracuje (najde položky blokování) pro danou událost typu AplServer.EventTransItem
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <param name="scannedEventsDict"></param>
        private void CollectBlockForEventAplTrans(EventInfo eventInfo, Dictionary<int, EventInfo> scannedEventsDict)
        {
            AplServer.EventTransItem transEvent = eventInfo.EventItem as AplServer.EventTransItem;
            if (transEvent != null)
            {
                // Zvětší interval (čas s korekcí) o 1 sec na každou stranu:
                TimeRange timeRange = eventInfo.TimeWithOffset;
                timeRange.Change(-1d, +1d);

                this.CollectBlockForSpid(transEvent.SqlPId, timeRange, scannedEventsDict);
            }
        }

        private void CollectBlockForSpid(int spid, TimeRange timeRange, Dictionary<int, EventInfo> scannedEventsDict)
        {
            List<EventInfo> eventList = new List<EventInfo>();
            List<ProcessInfo> processes = this.GetDbBlockingProcessList(timeRange);
            foreach (ProcessInfo processInfo in processes)
            {   // Počet procesů odpovídá počtu databází, pro které se blokování sleduje. Může být 0 (nesleduje se), nebo víc než 1 (produkční databáze + tempdb):
                // Vyfiltruji si události (EventInfo), které odpovídají zadání (blokování, SPID, čas):
                IEnumerable<EventInfo> eventInfos = processInfo.Events.Where(ei => FindDbBlockingItemOne(ei, spid, timeRange));
                // ... a přidám je do výstupu, volající metoda si je přebere:
                foreach (EventInfo eventInfo in eventInfos)
                {
                    if (!scannedEventsDict.ContainsKey(eventInfo.Id))
                    {
                        eventList.Add(eventInfo);
                        scannedEventsDict.Add(eventInfo.Id, eventInfo);
                    }
                }
            }

            foreach (EventInfo eventInfo in eventList)
                this.CollectBlockForEventDbBlock(eventInfo, scannedEventsDict);
        }
        /// <summary>
        /// Zjistí, zda daná událost vyhovuje požadavkům: Jde o DbBlocking.EventItem, týká se daného SPIDu a daného časového úseku. Pak vrátí true.
        /// </summary>
        /// <param name="ei"></param>
        /// <param name="spid"></param>
        /// <param name="timeRange"></param>
        /// <returns></returns>
        protected static bool FindDbBlockingItemOne(EventInfo ei, int spid, TimeRange timeRange)
        {
            DbBlocking.EventItem item = ei.EventItem as DbBlocking.EventItem;
            if (item == null) return false;
            TimeRange eiTime = ei.TimeWithOffset;
            eiTime.Change(-1d, +1d);                   // Zvětší interval o 1 sec na každou stranu
            if (!ei.TimeWithOffset.IsConjunct(timeRange)) return false;
            return (item.Process1Blocked.SpId == spid || item.Process2Active.SpId == spid);
        }
        /// <summary>
        /// Najde a vrátí procesy ze serverů typu DbBlocking.
        /// Projde servery v předaném MainInfo, a ze serverů s názvem = NorisBlockingConstants.ServerName načte jejich procesy do jednoho Listu.
        /// </summary>
        /// <param name="timeRange">Čas, pro který musí být načtené trace soubory, abych skutečně hledal v načtených datech</param>
        /// <returns></returns>
        protected List<ProcessInfo> GetDbBlockingProcessList(TimeRange timeRange)
        {
            List<ProcessInfo> result = new List<ProcessInfo>();
            foreach (ServerInfo serverInfo in this.TraceDataMain.Servers.Where(server => String.Equals(server.ServerName, Green.DbBlocking.NorisBlockingConstants.ServerName, StringComparison.InvariantCultureIgnoreCase)))
            {   // Vyhledám si servery, obsahující trace blokování:
                // Zajistím, že server bude mít načtena data pro zvolený čas:
                serverInfo.CheckLoadContentForTime(timeRange);

                // Načtu a do výstupu vložím procesy (u serveru typu NorisBlockingConstants je process = databáze):
                foreach (ProcessInfo processInfo in serverInfo.Processes)
                    result.Add(processInfo);
            }
            return result;
        }




        #endregion
        #region Vyhledání procesů s daty o blokování, vyhledání záznamů o blokování
        /// <summary>
        /// Najde seznam blokování pro vstupní záznamy funkce.
        /// Nejprve vyhledá vstupní data, a k nim pak následně v událostech blokování dohledá návazné blokování.
        /// </summary>
        /// <param name="spid"></param>
        /// <param name="time"></param>
        protected List<DbBlockInfo> FindBlocking(FunctionContextMenuItemRunArgs args)
        {
            List<DbBlockInfo> result = new List<DbBlockInfo>();
            Dictionary<int, EventInfo> scannedEventsDict = new Dictionary<int, EventInfo>();

            // List<ProcessInfo> blockingProdessList = this.GetDbBlockingProcessList(this.TraceDataMain,
            
            // Načtu a zpracuji jednoduché SQL položky (pocházet mohou z SQL serveru, nebo z aplikačního trace):
            List<ISqlEventData> iSqlItems = this.GetInputEventsOfType<ISqlEventData>(args);
            foreach (ISqlEventData iSqlItem in iSqlItems)
                this.AddBlockingForSql(iSqlItem, result, scannedEventsDict);

            // Načtu a zpracuji události Blokování:
            List<DbBlocking.EventItem> dbBlockItems = this.GetInputEventsOfType<DbBlocking.EventItem>(args);
            foreach (DbBlocking.EventItem dbBlockItem in dbBlockItems)
                this.AddBlockingForBlock(dbBlockItem, result, scannedEventsDict);
                
            return result;
        }
        /// <summary>
        /// Najde a vrátí sadu informací o blokování.
        /// Vyhledává data pro určitou SQL událost.
        /// </summary>
        /// <param name="iSqlItem">Událost, kterou uživatel vybral, pro kterou se hledá blokování</param>
        /// <param name="blockInfoList">Sem se přidávají výsledky, anebo se zdejší data modifikují (pokud najdu další blok pro shodný SPID a kontinuální čas)</param>
        /// <param name="scannedEventsDict">Dosud scanované události, ty se už scanovat nemusejí. Nové se sem přidávají.</param>
        private void AddBlockingForSql(ISqlEventData iSqlItem, List<DbBlockInfo> blockInfoList, Dictionary<int, EventInfo> scannedEventsDict)
        {
            
        }
        /// <summary>
        /// Najde a vrátí sadu informací o blokování.
        /// Vyhledává data pro určitou uživatelem vybranou událost blokování.
        /// </summary>
        /// <param name="dbBlockItem">Událost, kterou uživatel vybral, pro kterou se hledá blokování</param>
        /// <param name="blockInfoList">Sem se přidávají výsledky, anebo se zdejší data modifikují (pokud najdu další blok pro shodný SPID a kontinuální čas)</param>
        /// <param name="scannedEventsDict">Dosud scanované události, ty se už scanovat nemusejí. Nové se sem přidávají.</param>
        private void AddBlockingForBlock(DbBlocking.EventItem dbBlockItem, List<DbBlockInfo> blockInfoList, Dictionary<int, EventInfo> scannedEventsDict)
        {
            TimeRange totalTimeRange = dbBlockItem.Time;

            this.AddBlockingForBlockOneSpid(dbBlockItem.Process2Active.SpId, ref totalTimeRange, blockInfoList, scannedEventsDict);
            this.AddBlockingForBlockOneSpid(dbBlockItem.Process1Blocked.SpId, ref totalTimeRange, blockInfoList, scannedEventsDict);
        }
        private void AddBlockingForBlockOneSpid(int spid, ref TimeRange totalTimeRange, List<DbBlockInfo> blockInfoList, Dictionary<int, EventInfo> scannedEventsDict)
        {
            // Které další SPIDy budu hledat v návaznosti na nově nalezené bloky:
            Dictionary<int, object> nextSpids = new Dictionary<int, object>();
            nextSpids.Add(spid, null);

            // Pro jeden vstupní SPID a čas najdu bloky s dalšími SPIDy v daném čase, vyjma události v Dictionary scannedEventsDict:
            List<DbBlocking.EventItem> blockItemList = this.FindNewDbBlockingItems(spid, totalTimeRange, scannedEventsDict);
            foreach (DbBlocking.EventItem blockItem in blockItemList)
            {   // Tento blok vidím poprvé (dosud nebyl v scannedEventsDict, už tam je):
                totalTimeRange.ExpandTo(blockItem.Time);                  // Celkový čas blokování kolem jednoho vstupního prvku, postupně se rozšiřuje o sousední související bloky

                // Pokud ve výsledném seznamu blockInfoList dosud není záznam pro aktivní proces, dám ho tam:
                if (!blockInfoList.Any(b => b.Spid == blockItem.Process2Active.SpId && b.Time.IsConjunct(blockItem.Time)))
                {
                    blockInfoList.Add(DbBlockInfo.CreateActive(blockItem.Id, blockItem.Process2Active.SpId, blockItem.Time,  blockItem.ObjIdxName, blockItem.Process1Blocked.SpId));
                    AddBlockingNextSpid(nextSpids, blockItem.Process1Blocked.SpId, blockItem.Process2Active.SpId);
                }

                // Pokud ve výsledném seznamu blockInfoList dosud není záznam pro blokovaný proces, dám ho tam:
                if (!blockInfoList.Any(b => b.Spid == blockItem.Process1Blocked.SpId && b.Time.IsConjunct(blockItem.Time)))
                {
                    blockInfoList.Add(DbBlockInfo.CreateBlocked(blockItem.Id, blockItem.Process1Blocked.SpId, blockItem.Time, blockItem.ObjIdxName, blockItem.Process2Active.SpId));
                    AddBlockingNextSpid(nextSpids, blockItem.Process1Blocked.SpId, blockItem.Process2Active.SpId);
                }
            }

            // Zopakuje to pro podřízené SPIDy:
            foreach (int nextSpid in nextSpids.Keys)
            {
                if (nextSpid != spid)
                    AddBlockingForBlockOneSpid(nextSpid, ref totalTimeRange, blockInfoList, scannedEventsDict);
            }

        }
        /// <summary>
        /// Přidá procesy SPID 1 a 2 do seznamu pro příští hledání (nextSpid)
        /// </summary>
        /// <param name="nextSpids"></param>
        /// <param name="spid1"></param>
        /// <param name="spid2"></param>
        private void AddBlockingNextSpid(Dictionary<int, object> nextSpids, int spid1, int spid2)
        {
            if (!nextSpids.ContainsKey(spid1))
                nextSpids.Add(spid1, null);
            if (!nextSpids.ContainsKey(spid2))
                nextSpids.Add(spid2, null);
        }
        /// <summary>
        /// Najde a vrátí všechny položky DbBlocking, které se týkají daného SPIDu (ať jako blokující, nebo jako blokovaný) v daném čase, na libovolné tabulce.
        /// pouze ty, které dosud nejsou v dictionary scannedBlockDict (klíč = ID položky EventInfo)
        /// </summary>
        /// <param name="spid"></param>
        /// <param name="timeRange"></param>
        /// <returns></returns>
        private List<DbBlocking.EventItem> FindNewDbBlockingItems(int spid, TimeRange timeRange, Dictionary<int, EventInfo> scannedEventsDict)
        {
            List<DbBlocking.EventItem> result = new List<DbBlocking.EventItem>();

            TimeRange timeRangeExt = timeRange;
            timeRangeExt.Change(-1d, +1d);             // Zvětší interval o 1 sec na každou stranu

            List<ProcessInfo> processes = this.GetDbBlockingProcessList(timeRange);
            foreach (ProcessInfo processInfo in processes)
            {   // Počet procesů odpovídá počtu databází, pro které se blokování sleduje. Může být 0 (nesleduje se), nebo víc než 1 (produkční databáze + tempdb):
                // Vyfiltruji si události (EventInfo), které odpovídají zadání (blokování, SPID, čas):
                IEnumerable<EventInfo> eventInfos = processInfo.Events.Where(ei => FindDbBlockingItemOne(ei, spid, timeRangeExt));
                // ... a přidám je do výstupu, volající metoda si je přebere:
                foreach (EventInfo eventInfo in eventInfos)
                {
                    if (!scannedEventsDict.ContainsKey(eventInfo.Id))
                    {
                        DbBlocking.EventItem eventItem = eventInfo.EventItem as DbBlocking.EventItem;
                        scannedEventsDict.Add(eventInfo.Id, eventInfo);
                        result.Add(eventItem);
                    }
                }
            }
            return result;
        }








        /// <summary>
        /// Najde seznam blokování pro vstupní záznamy funkce.
        /// Nejprve vyhledá vstupní data, a k nim pak následně v událostech blokování dohledá návazné blokování.
        /// </summary>
        /// <param name="spid"></param>
        /// <param name="time"></param>
        protected List<DbBlockInfo> xFindBlocking(FunctionContextMenuItemRunArgs args)
        {
            List<DbBlockInfo> result = new List<DbBlockInfo>();

            foreach (ServerInfo serverInfo in this.TraceDataMain.Servers.Where(server => String.Equals(server.ServerName, Green.DbBlocking.NorisBlockingConstants.ServerName, StringComparison.InvariantCultureIgnoreCase)))
            {   // Vyhledám si servery obsahující trace blokování, a v nich budu hledat návazné SPID a jejich časy blokování (podle jednotlivých tabulek):
                foreach (ProcessInfo processInfo in serverInfo.Processes)
                {   // ProcessInfo odpovídá jednomu SQL serveru, typicky je jen jeden:
                    foreach (EventInfo eventInfo in processInfo.Events)
                    {   // EventInfo je jedna událost blokování (jedna tabulka, jeden časový úsek, dva SPID: blokující a blokovaný)

                    }
                }
            }
            return result;
        }
        /// <summary>
        /// Najde ve vybraných elementech taková data, která mohou být podkladem pro hledání blokování
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        protected List<DbBlockInfo> GetSelectedBlockInfoList(FunctionContextMenuItemRunArgs args, Int32? maxCount)
        {
            List<DbBlockInfo> result = new List<DbBlockInfo>();

            // Načtu a zpracuji jednoduché SQL položky (pocházet mohou z SQL serveru, nebo z aplikačního trace):
            List<ISqlEventData> iSqlItems = this.GetInputEventsOfType<ISqlEventData>(args, maxCount);
            if (iSqlItems != null && iSqlItems.Count > 0)
            {
                foreach (ISqlEventData iSqlItem in iSqlItems)
                    result.Add(new DbBlockInfo(0, iSqlItem.SqlPId) { Time = iSqlItem.Time });
            }
            if (maxCount.HasValue && result.Count >= maxCount.Value)
                return result;

            // Načtu a zpracuji položky logu blokování:
            List<DbBlocking.EventItem> dbBlockItems = this.GetInputEventsOfType<DbBlocking.EventItem>(args, maxCount);
            if (dbBlockItems != null && dbBlockItems.Count > 0)
            {
                foreach (DbBlocking.EventItem dbBlockItem in dbBlockItems)
                {
                    DbBlockInfo active = new DbBlockInfo(dbBlockItem.Id, dbBlockItem.Process2Active.SpId) { Time = dbBlockItem.Time };
                    active.DbOwnedObjectList.Add(dbBlockItem.ObjIdxName);
                    active.DbBlockedSpidList.Add(dbBlockItem.Process1Blocked.SpId);
                    result.Add(active);

                    DbBlockInfo pasive = new DbBlockInfo(dbBlockItem.Id, dbBlockItem.Process1Blocked.SpId) { Time = dbBlockItem.Time };
                    pasive.DbWaitToObjectList.Add(dbBlockItem.ObjIdxName);
                    pasive.DbWaitToSpidList.Add(dbBlockItem.Process2Active.SpId);
                    result.Add(pasive);
                }
            }

            return result;
        }
        #endregion
        #region class DbBlockInfo : Informace o položce blokování
        /// <summary>
        /// Data o jedné položce blokování.
        /// Popisuje jeden SPID jednoho uživatele v uceleném čase (od kdy do kdy se účastní blokování), plus informaci o objektech, které se blokování účastní
        /// </summary>
        protected class DbBlockInfo
        {
            public DbBlockInfo(int blockId, int spid)
            {
                this.BlockId = blockId;
                this.Spid = spid;
                this.DbOwnedObjectList = new List<string>();
                this.DbWaitToObjectList = new List<string>();
                this.DbBlockedSpidList = new List<int>();
                this.DbWaitToSpidList = new List<int>();
            }
            public override string ToString()
            {
                return "SPID=" + this.Spid.ToString() + "; Time=" + this.Time.StringBeginAndSecs +
                    "; WaitToSpid={" + this.DbWaitToSpidList.ToStringList(",") +
                    "}; BlockingSpid={" + this.DbBlockedSpidList.ToStringList(",") + "}";
            }
            /// <summary>
            /// Klíč, obsahuje SPID a Time.Begin.
            /// Změnou Time (což je povolené) se změní klíč !!!
            /// Není dobré tedy ukládat klíč do Dictionary, data je nutno vždy scanovat s ohledem na jejich aktuální hodnoty.
            /// </summary>
            public string Key { get { return this.Spid.ToString("0000") + ":" + this.Time.Begin.ToString("yyyyMMdd-HHmmss.fff"); } }
            /// <summary>
            /// ID blokování. Pokud záznam pochází z SQL, pak je zde 0.
            /// </summary>
            public int BlockId { get; private set; }
            /// <summary>
            /// SPID procesu.
            /// </summary>
            public int Spid { get; private set; }
            /// <summary>
            /// Čas, po který tento blok trvá. Jeden blok je jeden kontinuální čas.
            /// Pokud pro jeden SPID najdu více oddělených časových úseků, jde o dva různé bloky.
            /// Typicky: SPID 100 čeká na SPID 80 po čas 20-25, v čase 25 to vzdá (je odstřelen na deadlock) a končí.
            /// Jeho SPID 100 dostane jiná funkce, a taky začne čekat, po čas 55-75. Jde o jinou funkci, jiný login.
            /// </summary>
            public TimeRange Time { get; set; }
            /// <summary>
            /// Dohledaný login
            /// </summary>
            public string Login { get; set; }
            /// <summary>
            /// Blokuji někoho? Ano, pokud vlastním některé objekty a někdo na ně čeká.
            /// </summary>
            public bool IsBlocking { get { return (this.DbOwnedObjectList.Count > 0 && this.DbBlockedSpidList.Count > 0); } }
            /// <summary>
            /// Jsem blokován? Ano, pokud čekám na nějaký objekt kvůli nějakému SPIDu
            /// </summary>
            public bool IsBlocked { get { return (this.DbWaitToObjectList.Count > 0 && this.DbWaitToSpidList.Count > 0); } }
            /// <summary>
            /// Objekty, které uživatel vlastní, skrze které blokuje někoho dalšího
            /// </summary>
            public List<string> DbOwnedObjectList { get; private set; }
            /// <summary>
            /// Objekt(y), na které uživatel čeká. Typicky bude 0 nebo 1.
            /// </summary>
            public List<string> DbWaitToObjectList { get; private set; }
            /// <summary>
            /// Seznam SPIDů, které blokuji
            /// </summary>
            public List<int> DbBlockedSpidList { get; private set; }
            /// <summary>
            /// Seznam SPIDů, kvůli kterým čekám
            /// </summary>
            public List<int> DbWaitToSpidList { get; private set; }
            /// <summary>
            /// Vrátí objekt popisující blok pro SPID aktivní = ten kdo někoho blokuje
            /// </summary>
            /// <param name="blockId"></param>
            /// <param name="activeSPId"></param>
            /// <param name="timeRange"></param>
            /// <param name="objectName"></param>
            /// <param name="blockedSPId"></param>
            /// <returns></returns>
            internal static DbBlockInfo CreateActive(int blockId, int activeSPId, TimeRange timeRange, string objectName, int blockedSPId)
            {
                DbBlockInfo info = new DbBlockInfo(blockId, activeSPId);
                info.Time = timeRange;
                info.DbOwnedObjectList.Add(objectName);
                info.DbBlockedSpidList.Add(blockedSPId);
                return info;
            }
            /// <summary>
            /// Vrátí objekt popisující blok pro SPID čekající = ten, kdo čeká
            /// </summary>
            /// <param name="blockId"></param>
            /// <param name="blockedSPId"></param>
            /// <param name="timeRange"></param>
            /// <param name="objectName"></param>
            /// <param name="activeSPId"></param>
            /// <returns></returns>
            internal static DbBlockInfo CreateBlocked(int blockId, int blockedSPId, TimeRange timeRange, string objectName, int activeSPId)
            {
                DbBlockInfo info = new DbBlockInfo(blockId, blockedSPId);
                info.Time = timeRange;
                info.DbWaitToObjectList.Add(objectName);
                info.DbWaitToSpidList.Add(activeSPId);
                return info;
            }
        }
        #endregion
        protected MainInfo TraceDataMain;
    }
}
